package com.octopus.tree.imp.simple;

import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import com.octopus.string.StringUtils;
import com.octopus.tree.Tree;
import com.octopus.tree.TreeBranch;
import com.octopus.tree.TreeLeaf;
import com.octopus.tree.Unit;
import com.octopus.tree.structure.NType;
import com.octopus.tree.structure.ObjectFactory;
import com.octopus.tree.structure.Root;

public class TreeImp implements Tree {

	Root realRoot;
	TreeBranchImp root;

	private static final String DEFAULT_ROOT = "root";

	public TreeImp(String rootName) {
		init(rootName);
	}
	
	public TreeImp(String rootNameOrXmlString, boolean isXmlString) throws JAXBException{
		if(isXmlString){
			try {
				JAXBContext jaxbContext = JAXBContext.newInstance("com.octopus.tree.structure");
				Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
				Source source =  new StreamSource(new StringReader(rootNameOrXmlString));
				Object object = unmarshaller.unmarshal(source);
				if(object instanceof Root){
					this.realRoot = (Root)object;
					NType nt = this.realRoot.getN();
					if(nt == null){
						ObjectFactory of = new ObjectFactory();
						nt = of.createNType();
						nt.setName(DEFAULT_ROOT);
						nt.setKind(null);
					}
					TreeBranchImp tbi = null;
					try {
						tbi = new TreeBranchImp(nt);
					} catch (NameAlreadyExistException e) {
						// Such thing will never happen
					} catch (AlreadyInTreeStructureException e) {
						// Such thing will never happen
					}
					tbi.setSystemTreeBranchProperty(SimpleImpConstants.SYSTEM_ROOT, "");
					this.realRoot.setN(tbi);
					this.root = tbi;
					if(this.root.toContentObject()){
						this.root.clearPermanent();
					}else{
						throw new JAXBException("Object transformation failed");
					}
				}else{
					throw new JAXBException("The string doesn't represent Root object");
				}
			} catch (JAXBException e) {
				throw e;
			}
		}else{
			init(rootNameOrXmlString);
		}
	}

	public void init(String rootName) {
		if (rootName == null) {
			rootName = DEFAULT_ROOT;
		} else {
			rootName = rootName.trim();
			if (rootName.length() == 0) {
				rootName = DEFAULT_ROOT;
			}
		}

		rootName = rootName.trim();
		ObjectFactory of = new ObjectFactory();
		realRoot = of.createRoot();
		NType rootNType = of.createNType();
		rootNType.setName(rootName);
		rootNType.setKind(null);
		TreeBranchImp tbi = null;
		try {
			tbi = new TreeBranchImp(rootNType);
		} catch (NameAlreadyExistException e) {
			// Such thing will never happen
		} catch (AlreadyInTreeStructureException e) {
			// Such thing will never happen
		}
		tbi.setSystemTreeBranchProperty(SimpleImpConstants.SYSTEM_ROOT, "");
		root = tbi;
		realRoot.setN(root);
	}

	public TreeImp() {
		this(DEFAULT_ROOT);
	}

	public TreeImp(Root root) {
		if (root == null) {
			init(DEFAULT_ROOT);
		} else {
			this.realRoot = root;
			TreeBranchImp tbi = null;
			try {
				NType n = root.getN();
				if (n == null) {
					ObjectFactory of = new ObjectFactory();
					n = of.createNType();
					n.setName(DEFAULT_ROOT);
					this.realRoot.setN(n);
				}
				tbi = new TreeBranchImp(n);
			} catch (NameAlreadyExistException e) {
				// Such thing will never happen.
			} catch (AlreadyInTreeStructureException e) {
				// Such thing will never happen.
			}
			tbi.setKind(null);
			tbi.setSystemTreeBranchProperty(SimpleImpConstants.SYSTEM_ROOT, "");
			this.root = tbi;
			this.realRoot.setN(tbi);
		}
	}

	@Override
	public TreeBranch getRoot() {
		return root;
	}

	@Override
	public List<TreeBranch> getTreeBranchList(String branchPath) {
		StringUtils.StringContainer sc = new StringUtils.StringContainer(
				branchPath);
		if (StringUtils.isNullString(sc)) {
			return null;
		}
		String[] paths = sc.getAfterTrimedString().split("\\.");
		if (paths != null && paths.length >= 1) {
			int theLastIndex = paths.length - 1;
			TreeBranch parent = this.root;
			for (int i = 0; i <= theLastIndex; i++) {
				if (i == theLastIndex) {
					return parent.getSubTreeBranchListByName(paths[i].trim());
				} else {
					parent = parent.getDefaultSubTreeBrancByName(paths[i]
							.trim());
					if (parent == null) {
						return null;
					}
				}

			}
			return null;
		} else {
			return null;
		}
	}

	@Override
	public List<TreeLeaf> getTreeLeafList(String branchPath, String leafName) {
		TreeBranch tb = getDefaultTreeBranch(branchPath);
		if (tb != null) {
			return tb.getTreeLeafByName(leafName);
		}
		return null;
	}

	@Override
	public TreeBranch getDefaultTreeBranch(String branchPath) {
		StringUtils.StringContainer sc = new StringUtils.StringContainer(
				branchPath);
		if (StringUtils.isNullString(sc)) {
			return null;
		}
		String[] paths = sc.getAfterTrimedString().split("\\.");
		if (paths != null && paths.length >= 1) {
			int theLastIndex = paths.length - 1;
			TreeBranch parent = this.root;
			for (int i = 0; i <= theLastIndex; i++) {
				if (i == theLastIndex) {
					return parent.getDefaultSubTreeBrancByName(paths[i].trim());
				} else {
					parent = parent.getDefaultSubTreeBrancByName(paths[i]
							.trim());
					if (parent == null) {
						return null;
					}
				}

			}
			return null;
		} else {
			return null;
		}
	}

	@Override
	public List<TreeBranch> getTreeBranchList(String branchPath, String kind) {
		StringUtils.StringContainer sc = new StringUtils.StringContainer(
				branchPath);
		if (StringUtils.isNullString(sc)) {
			return null;
		}
		String[] paths = sc.getAfterTrimedString().split("\\.");
		if (paths != null && paths.length >= 1) {
			int theLastIndex = paths.length - 1;
			TreeBranch parent = this.root;
			for (int i = 0; i <= theLastIndex; i++) {
				if (i == theLastIndex) {
					return parent.getSubTreeBranchListByNameAndKind(
							paths[i].trim(), kind);
				} else {
					parent = parent.getDefaultSubTreeBrancByName(paths[i]
							.trim());
					if (parent == null) {
						return null;
					}
				}

			}
			return null;
		} else {
			return null;
		}
	}

	@Override
	public TreeLeaf getDefaultTreeLeaf(String branchPath, String leafName) {
		TreeBranch tb = getDefaultTreeBranch(branchPath);
		if (tb != null) {
			return tb.getDefaultTreeLeafByName(leafName);
		}
		return null;
	}

	@Override
	public List<TreeLeaf> getTreeLeaf(String branchPath, String leafName,
			String kind) {
		TreeBranch tb = getDefaultTreeBranch(branchPath);
		if (tb != null) {
			return tb.getTreeLeafByNameAndKind(leafName, kind);
		}
		return null;
	}

	public static List<TreeBranch> findTreeBranch(List<Unit> rules,
			TreeBranch treeBranch) {
		if (rules == null || rules.isEmpty() || treeBranch == null) {
			return null;
		} else {
			Unit rule = rules.get(0);
			String name = rule.getName();
			String kind = rule.getKind();
			Integer startIndex = rule.getStartIndex();
			Integer endIndex = rule.getEndIndex();
			List<String> keys = rule.getPropertyKeyList();
			List<TreeBranch> treeBranchList = null;
			if ("*".equals(name)) {
				if (SimpleImpConstants.DEFAULT_KIND_SIGN.equalsIgnoreCase(kind)) {
					treeBranchList = treeBranch.getDefaultAllSubBranch();
				} else {
					treeBranchList = treeBranch.getAllSubBranch();
				}
			} else {
				if (SimpleImpConstants.DEFAULT_KIND_SIGN.equalsIgnoreCase(kind)) {
					TreeBranch tb = treeBranch
							.getDefaultSubTreeBrancByName(name);
					treeBranchList = new ArrayList<TreeBranch>();
					if(tb != null){
						treeBranchList.add(tb);
					}
				} else {
					treeBranchList = treeBranch
							.getSubTreeBranchListByName(name);
				}
			}

			if (treeBranchList == null || treeBranchList.isEmpty()) {
				return null;
			}

			if (kind != null && kind.length() > 0) {
				if (!SimpleImpConstants.DEFAULT_KIND_SIGN
						.equalsIgnoreCase(kind)) {
					for (int i = treeBranchList.size() - 1; i >= 0; i--) {
						TreeBranch tb = treeBranchList.get(i);
						if (!kind.equals(tb.getTreeBranchKind())) {
							treeBranchList.remove(i);
						}
					}
				}
			}

			if (!treeBranchList.isEmpty()) {
				if (startIndex != null && endIndex != null) {
					for (int i = treeBranchList.size() - 1; i >= 0; i--) {
						if (i < startIndex || i > endIndex) {
							treeBranchList.remove(i);
							continue;
						}
					}
				}
				if (!treeBranchList.isEmpty()) {
					if (keys != null && !keys.isEmpty()) {
						for (int i = treeBranchList.size() - 1; i >= 0; i--) {
							boolean propertiesCheckPass = true;
							TreeBranch tb = treeBranchList.get(i);
							for (String key : keys) {
								String rulePropertyValue = rule
										.getPropertyValue(key);
								String propertyValue = tb
										.getTreeBranchProperty(key);
								if (!StringUtils.stringEqualsIgnoreCase(
										rulePropertyValue, propertyValue)) {
									propertiesCheckPass = false;
									break;
								}
							}
							if (!propertiesCheckPass) {
								treeBranchList.remove(i);
							}
						}
					}
				}
			}
			if (rules.size() == 1) {
				if (treeBranchList.isEmpty()) {
					return null;
				} else {
					return treeBranchList;
				}
			}
			List<Unit> newRules = new ArrayList<Unit>();
			newRules.addAll(rules.subList(1, rules.size()));
			List<TreeBranch> result = new ArrayList<TreeBranch>();
			if (rules.size() > 0 && !treeBranchList.isEmpty()) {
				for (TreeBranch tb : treeBranchList) {
					List<TreeBranch> ltb = findTreeBranch(newRules, tb);
					if (ltb != null) {
						result.addAll(ltb);
					}
				}
				if (result.isEmpty()) {
					return null;
				} else {
					return result;
				}
			} else {
				return null;
			}
		}
	}

	public static List<TreeLeaf> findTreeLeaf(Unit rule, TreeBranch treeBranch) {
		if (rule == null || treeBranch == null) {
			return null;
		}
		String name = rule.getName();
		String kind = rule.getKind();
		Integer startIndex = rule.getStartIndex();
		Integer endIndex = rule.getEndIndex();
		List<String> keys = rule.getPropertyKeyList();

		if (name != null) {
			name = name.trim();
		}
		if (kind != null) {
			kind = kind.trim();
		}

		List<TreeLeaf> treeLeafList = null;
		if ("*".equals(name)) {
			if (SimpleImpConstants.DEFAULT_KIND_SIGN.equalsIgnoreCase(kind)) {
				treeLeafList = treeBranch.getDefaultTreeLeafList();
			} else {
				treeLeafList = treeBranch.getTreeLeafList();
			}
		} else {
			if (SimpleImpConstants.DEFAULT_KIND_SIGN.equalsIgnoreCase(kind)) {
				TreeLeaf tl = treeBranch.getDefaultTreeLeafByName(name);
				treeLeafList = new ArrayList<TreeLeaf>();
				if(tl != null){
					treeLeafList.add(tl);
				}				
			} else {
				treeLeafList = treeBranch.getTreeLeafByName(name);
			}
		}

		if (treeLeafList == null || treeLeafList.isEmpty()) {
			return null;
		}

		if (kind != null && kind.length() > 0) {
			if (!SimpleImpConstants.DEFAULT_KIND_SIGN.equalsIgnoreCase(kind)) {
				for (int i = treeLeafList.size() - 1; i >= 0; i--) {
					TreeLeaf tl = treeLeafList.get(i);
					if (!kind.equals(tl.getTreeLeafKind())) {
						treeLeafList.remove(i);
					}
				}
			}
		}

		if (!treeLeafList.isEmpty()) {
			if (startIndex != null && endIndex != null) {
				for (int i = treeLeafList.size() - 1; i >= 0; i--) {
					if (i < startIndex || i > endIndex) {
						treeLeafList.remove(i);
						continue;
					}
				}
			}
			if (!treeLeafList.isEmpty()) {
				if (keys != null && !keys.isEmpty()) {
					for (int i = treeLeafList.size() - 1; i >= 0; i--) {
						boolean propertiesCheckPass = true;
						TreeLeaf tl = treeLeafList.get(i);
						for (String key : keys) {
							String rulePropertyValue = rule
									.getPropertyValue(key);
							String propertyValue = tl.getTreeLeafProperty(key);
							if (!StringUtils.stringEqualsIgnoreCase(
									rulePropertyValue, propertyValue)) {
								propertiesCheckPass = false;
								break;
							}
						}
						if (!propertiesCheckPass) {
							treeLeafList.remove(i);
						}
					}
				}
			}

			if (treeLeafList.isEmpty()) {
				return null;
			}

			return treeLeafList;
		} else {
			return null;
		}
	}

	public static List<TreeLeaf> findTreeLeaf(List<Unit> rules,
			TreeBranch treeBranch) {
		if (rules == null || rules.isEmpty() || treeBranch == null) {
			return null;
		} else {
			Unit unit = rules.get(rules.size() - 1);
			if (rules.size() == 1) {
				return findTreeLeaf(unit, treeBranch);
			} else {
				List<Unit> newRules = new ArrayList<Unit>();
				newRules.addAll(rules.subList(0, rules.size() - 1));
				List<TreeBranch> treeBranchList = findTreeBranch(newRules,
						treeBranch);
				List<TreeLeaf> result = new ArrayList<TreeLeaf>();
				if (treeBranchList != null) {
					for (TreeBranch tb : treeBranchList) {
						List<TreeLeaf> temp = findTreeLeaf(unit, tb);
						if (temp != null) {
							result.addAll(temp);
						}
					}
					if (result.isEmpty()) {
						return null;
					} else {
						return result;
					}
				} else {
					return null;
				}
			}
		}
	}

	@Override
	public List<TreeLeaf> findTreeLeaf(String xpathLike) {
		List<Unit> rules = SimpleUtils.parseLikeXpath(xpathLike);
		if (rules != null && !rules.isEmpty()) {
			return findTreeLeaf(rules, this.root);
		}
		return null;
	}
	
	@Override
	public List<TreeBranch> findTreeBranch(String xpathLike){
		List<Unit> rules = SimpleUtils.parseLikeXpath(xpathLike);
		if (rules != null && !rules.isEmpty()) {
			return findTreeBranch(rules, this.root);
		}
		return null;
	}

	@Override
	public boolean preparePermanent() {
		if(this.root != null){
			return this.root.preparePermanent();
		}
		return false;
	}

	@Override
	public void clearPermanent() {
		if(this.root != null){
			this.root.clearPermanent();
		}
	}

	@Override
	public boolean toContentObject() {
		if(this.root != null){
			return this.root.toContentObject();
		}
		return false;
	}

	@Override
	public TreeBranch createSimpleSubTreeBranch(String pathExp) {
		if(this.root != null){
			return this.root.createSimpleSubTreeBranch(pathExp);
		}
		return null;
	}
	
	public String toPermanent(){
		if(this.realRoot == null || this.root == null){
			return null;
		}
		this.root.preparePermanent();
		String result =  this.toString();
		this.root.clearPermanent();
		return result;
	}
	
	public String toString(){
		if(this.realRoot == null || this.root == null){
			return null;
		}
		ByteArrayOutputStream baos = null;
		try {
			JAXBContext jaxbContext = JAXBContext.newInstance("com.octopus.tree.structure");
			Marshaller marshaller = jaxbContext.createMarshaller();
			baos = new ByteArrayOutputStream(); 
			marshaller.marshal(this.realRoot, baos);
			return baos.toString("UTF-8");
		} catch (JAXBException e) {
			return null;
		} catch (UnsupportedEncodingException e) {
			return null;
		} finally{
			if(baos != null){
				try{baos.close();}catch(Exception e){}
			}
		}
	}
}
